package com.founder.fix.fixflow.test.parallel;

import java.io.PrintWriter;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
import java.util.LinkedList;

import javax.sql.ConnectionEvent;
import javax.sql.ConnectionEventListener;
import javax.sql.ConnectionPoolDataSource;
import javax.sql.PooledConnection;

/**
 * A lightweight standalone JDBC connection pool manager.
 * 
 * <p>
 * The public methods of this class are thread-safe.
 * 
 * <p>
 * Home page: <a
 * href="http://www.source-code.biz/miniconnectionpoolmanager">www.
 * source-code.biz/miniconnectionpoolmanager</a><br>
 * Author: Christian d'Heureuse, Inventec Informatik AG, Zurich, Switzerland<br>
 * Multi-licensed: EPL / LGPL / MPL.
 */
public class MiniConnectionPoolManager {

	private ConnectionPoolDataSource dataSource;
	private int maxConnections;
	private long timeoutMs;
	private PrintWriter logWriter;
	private Semaphore semaphore;
	private LinkedList<PooledConnection> recycledConnections;
	private int activeConnections;
	private PoolConnectionEventListener poolConnectionEventListener;
	private boolean isDisposed;
	private boolean doPurgeConnection;

	/**
	 * Thrown in {@link #getConnection()} or {@link #getValidConnection()} when
	 * no free connection becomes available within <code>timeout</code> seconds.
	 */
	public static class TimeoutException extends RuntimeException {
		private static final long serialVersionUID = 1;

		public TimeoutException() {
			super("Timeout while waiting for a free database connection.");
		}

		public TimeoutException(String msg) {
			super(msg);
		}
	}

	/**
	 * Constructs a MiniConnectionPoolManager object with a timeout of 60
	 * seconds.
	 * 
	 * @param dataSource
	 *            the data source for the connections.
	 * @param maxConnections
	 *            the maximum number of connections.
	 */
	public MiniConnectionPoolManager(ConnectionPoolDataSource dataSource, int maxConnections) {
		this(dataSource, maxConnections, 60);
	}

	/**
	 * Constructs a MiniConnectionPoolManager object.
	 * 
	 * @param dataSource
	 *            the data source for the connections.
	 * @param maxConnections
	 *            the maximum number of connections.
	 * @param timeout
	 *            the maximum time in seconds to wait for a free connection.
	 */
	public MiniConnectionPoolManager(ConnectionPoolDataSource dataSource, int maxConnections, int timeout) {
		this.dataSource = dataSource;
		this.maxConnections = maxConnections;
		this.timeoutMs = timeout * 1000L;
		try {
			logWriter = dataSource.getLogWriter();
		} catch (SQLException e) {
		}
		if (maxConnections < 1) {
			throw new IllegalArgumentException("Invalid maxConnections value.");
		}
		semaphore = new Semaphore(maxConnections, true);
		recycledConnections = new LinkedList<PooledConnection>();
		poolConnectionEventListener = new PoolConnectionEventListener();
	}

	/**
	 * Closes all unused pooled connections.
	 */
	public synchronized void dispose() throws SQLException {
		if (isDisposed) {
			return;
		}
		isDisposed = true;
		SQLException e = null;
		while (!recycledConnections.isEmpty()) {
			PooledConnection pconn = recycledConnections.remove();
			try {
				pconn.close();
			} catch (SQLException e2) {
				if (e == null) {
					e = e2;
				}
			}
		}
		if (e != null) {
			throw e;
		}
	}

	/**
	 * Retrieves a connection from the connection pool.
	 * 
	 * <p>
	 * If <code>maxConnections</code> connections are already in use, the method
	 * waits until a connection becomes available or <code>timeout</code>
	 * seconds elapsed. When the application is finished using the connection,
	 * it must close it in order to return it to the pool.
	 * 
	 * @return a new <code>Connection</code> object.
	 * @throws TimeoutException
	 *             when no connection becomes available within
	 *             <code>timeout</code> seconds.
	 */
	public Connection getConnection() throws SQLException {
		return getConnection2(timeoutMs);
	}

	private Connection getConnection2(long timeoutMs) throws SQLException {
		// This routine is unsynchronized, because semaphore.tryAcquire() may
		// block.
		synchronized (this) {
			if (isDisposed) {
				throw new IllegalStateException("Connection pool has been disposed.");
			}
		}
		try {
			if (!semaphore.tryAcquire(timeoutMs, TimeUnit.MILLISECONDS)) {
				throw new TimeoutException();
			}
		} catch (InterruptedException e) {
			throw new RuntimeException("Interrupted while waiting for a database connection.", e);
		}
		boolean ok = false;
		try {
			Connection conn = getConnection3();
			ok = true;
			return conn;
		} finally {
			if (!ok) {
				semaphore.release();
			}
		}
	}

	private synchronized Connection getConnection3() throws SQLException {
		if (isDisposed) {
			throw new IllegalStateException("Connection pool has been disposed.");
		} // test again with lock
		PooledConnection pconn;
		if (!recycledConnections.isEmpty()) {
			pconn = recycledConnections.remove();
		} else {
			pconn = dataSource.getPooledConnection();
			pconn.addConnectionEventListener(poolConnectionEventListener);
		}
		Connection conn = pconn.getConnection();
		activeConnections++;
		assertInnerState();
		return conn;
	}

	/**
	 * Retrieves a connection from the connection pool and ensures that it is
	 * valid by calling {@link Connection#isValid(int)}.
	 * 
	 * <p>
	 * If a connection is not valid, the method tries to get another connection
	 * until one is valid (or a timeout occurs).
	 * 
	 * <p>
	 * Pooled connections may become invalid when e.g. the database server is
	 * restarted.
	 * 
	 * <p>
	 * This method is slower than {@link #getConnection()} because the JDBC
	 * driver has to send an extra command to the database server to test the
	 * connection.
	 * 
	 * 
	 * <p>
	 * This method requires Java 1.6 or newer.
	 * 
	 * @throws TimeoutException
	 *             when no valid connection becomes available within
	 *             <code>timeout</code> seconds.
	 */
	public Connection getValidConnection() {
		long time = System.currentTimeMillis();
		long timeoutTime = time + timeoutMs;
		int triesWithoutDelay = getInactiveConnections() + 1;
		while (true) {
			Connection conn = getValidConnection2(time, timeoutTime);
			if (conn != null) {
				return conn;
			}
			triesWithoutDelay--;
			if (triesWithoutDelay <= 0) {
				triesWithoutDelay = 0;
				try {
					Thread.sleep(250);
				} catch (InterruptedException e) {
					throw new RuntimeException("Interrupted while waiting for a valid database connection.", e);
				}
			}
			time = System.currentTimeMillis();
			if (time >= timeoutTime) {
				throw new TimeoutException("Timeout while waiting for a valid database connection.");
			}
		}
	}

	private Connection getValidConnection2(long time, long timeoutTime) {
		long rtime = Math.max(1, timeoutTime - time);
		Connection conn;
		try {
			conn = getConnection2(rtime);
		} catch (SQLException e) {
			return null;
		}
		rtime = timeoutTime - System.currentTimeMillis();
		@SuppressWarnings("unused")
		int rtimeSecs = Math.max(1, (int) ((rtime + 999) / 1000));
		try {
			if (conn!=null) {
				return conn;
			}
		} catch (Exception e) {
			
		}
		// This Exception should never occur. If it nevertheless occurs, it's
		// because of an error in the
		// JDBC driver which we ignore and assume that the connection is not
		// valid.
		// When isValid() returns false, the JDBC driver should have already
		// called connectionErrorOccurred()
		// and the PooledConnection has been removed from the pool, i.e. the
		// PooledConnection will
		// not be added to recycledConnections when Connection.close() is
		// called.
		// But to be sure that this works even with a faulty JDBC driver, we
		// call purgeConnection().
		purgeConnection(conn);
		return null;
	}

	// Purges the PooledConnection associated with the passed Connection from
	// the connection pool.
	private synchronized void purgeConnection(Connection conn) {
		try {
			doPurgeConnection = true;
			// (A potential problem of this program logic is that setting the
			// doPurgeConnection flag
			// has an effect only if the JDBC driver calls connectionClosed()
			// synchronously within
			// Connection.close().)
			conn.close();
		} catch (SQLException e) {
		}
		// ignore exception from close()
		finally {
			doPurgeConnection = false;
		}
	}

	private synchronized void recycleConnection(PooledConnection pconn) {
		if (isDisposed || doPurgeConnection) {
			disposeConnection(pconn);
			return;
		}
		if (activeConnections <= 0) {
			throw new AssertionError();
		}
		activeConnections--;
		semaphore.release();
		recycledConnections.add(pconn);
		assertInnerState();
	}

	private synchronized void disposeConnection(PooledConnection pconn) {
		pconn.removeConnectionEventListener(poolConnectionEventListener);
		if (!recycledConnections.remove(pconn)) {
			// If the PooledConnection is not in the recycledConnections list,
			// we assume that the connection was active.
			if (activeConnections <= 0) {
				throw new AssertionError();
			}
			activeConnections--;
			semaphore.release();
		}
		closeConnectionAndIgnoreException(pconn);
		assertInnerState();
	}

	private void closeConnectionAndIgnoreException(PooledConnection pconn) {
		try {
			pconn.close();
		} catch (SQLException e) {
			log("Error while closing database connection: " + e.toString());
		}
	}

	private void log(String msg) {
		String s = "MiniConnectionPoolManager: " + msg;
		try {
			if (logWriter == null) {
				System.err.println(s);
			} else {
				logWriter.println(s);
			}
		} catch (Exception e) {
		}
	}

	private void assertInnerState() {
		if (activeConnections < 0) {
			throw new AssertionError();
		}
		if (activeConnections + recycledConnections.size() > maxConnections) {
			throw new AssertionError();
		}
		if (activeConnections + semaphore.availablePermits() > maxConnections) {
			throw new AssertionError();
		}
	}

	private class PoolConnectionEventListener implements ConnectionEventListener {
		public void connectionClosed(ConnectionEvent event) {
			PooledConnection pconn = (PooledConnection) event.getSource();
			recycleConnection(pconn);
		}

		public void connectionErrorOccurred(ConnectionEvent event) {
			PooledConnection pconn = (PooledConnection) event.getSource();
			disposeConnection(pconn);
		}
	}

	/**
	 * Returns the number of active (open) connections of this pool.
	 * 
	 * <p>
	 * This is the number of <code>Connection</code> objects that have been
	 * issued by {@link #getConnection()}, for which
	 * <code>Connection.close()</code> has not yet been called.
	 * 
	 * @return the number of active connections.
	 **/
	public synchronized int getActiveConnections() {
		return activeConnections;
	}

	/**
	 * Returns the number of inactive (unused) connections in this pool.
	 * 
	 * <p>
	 * This is the number of internally kept recycled connections, for which
	 * <code>Connection.close()</code> has been called and which have not yet
	 * been reused.
	 * 
	 * @return the number of inactive connections.
	 **/
	public synchronized int getInactiveConnections() {
		return recycledConnections.size();
	}

} // end class MiniConnectionPoolManager